package com.atguigu.servicebase;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;

/**
 * 缓存管理器配置类
 * @author He Wentao
 * @since 2022/9/14 19:06
 */

@EnableCaching // 开启 spring 缓存功能
@Configuration // 声明这个类是配置类
public class RedisConfig extends CachingConfigurerSupport {

    @Bean(name = "template")
    public RedisTemplate<String, Object> template(RedisConnectionFactory factory) {

        //  创建 RedisTemplate<String, Object>对象，配置完成之后，注入到容器中去
        RedisTemplate<String, Object> template = new RedisTemplate<>();

        //  传入 Redis链接工厂，用于后面的构建 RedisTemplate
        template.setConnectionFactory(factory);
        //  创建序列化配置对象
        Jackson2JsonRedisSerializer<Object> jacksonSerializer= new Jackson2JsonRedisSerializer<>(Object.class);
        //  在这个方法中，对序列化对象进行相关配置
        getJacksonConfig(jacksonSerializer);

        //  设置 Redis 普通的 Value 值的序列化方案，使用前面配置的 Jackson 序列化配置
        template.setValueSerializer(jacksonSerializer);
        //  设置 Redis hash 类型的value序列化方案，使用前面配置的 Jackson 序列化配置
        template.setHashValueSerializer(jacksonSerializer);

        //  创建 String 类型序列化器
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        //  设置 Redis key序列化方式，采用 默认的 String类型 序列化器
        template.setKeySerializer(stringRedisSerializer);
        //  设置 Redis hash 的 key，也采用 默认的 String类型 序列化器
        template.setHashKeySerializer(stringRedisSerializer);

        // 属性设置完成，调用初始化方法，进行配置
        template.afterPropertiesSet();
        // 返回 redisTemplate 到容器中，方便后续使用
        return template;
    }

    /**
     * 自定义Jackson 配置对象
     * @param jacksonSerial Jackson 对象
     */
    private void getJacksonConfig(Jackson2JsonRedisSerializer<Object> jacksonSerial) {
        // Jackson 核心对象，能将 对象 与 JSON 互相转换
        ObjectMapper om = new ObjectMapper();
        // 指定要序列化的域（也就是是否序列化 get和set，以及修饰符范围，ANY是都有包括 private 和 public）
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 指定序列化输入的类型，类必须是非 final 修饰的，final修饰的类，比如String,Integer等会报异常
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
                ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        // 设置 序列化使用对象映射器
        jacksonSerial.setObjectMapper(om);
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {

        // 根据 redis 链接工厂，进行构建缓存管理器，并且注入到容器中
        return RedisCacheManager.builder(factory)
                // 配置缓存，这里是自定义缓存配置方法
                .cacheDefaults(defaultCacheConfig(10000))
                // 开启缓存事务
                .transactionAware()
                // 构建缓存管理器
                .build();
    }

    /**
     * 自定义缓存配置方法
     * @param second 缓存失效时间
     * @return 缓存配置对象
     */
    private RedisCacheConfiguration defaultCacheConfig(Integer second) {
        // 创建 jackson 中，对 redis 序列化支持的对象
        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);

        //  在这个方法中，对序列化对象进行相关配置
        getJacksonConfig(serializer);

        // 使用 Redis缓存配置对象，进行配置相关设置
        return RedisCacheConfiguration.defaultCacheConfig()
                // 设置缓存失效时间
                .entryTtl(Duration.ofSeconds(second))
                // 设置序列化 key 使用 String 序列化器
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                // 设置序列化 value 使用 Jackson 序列化器
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer))
                // 设置不允许设置空值，会出错
                .disableCachingNullValues();
    }

}
